package org.ala.distributed_transaction_commons;

import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.ala.distributed_transaction_commons.communication.Coordinator;
import org.ala.distributed_transaction_commons.communication.Participant;
import org.ala.distributed_transaction_commons.logs.LoggerFactory;
import org.ala.distributed_transaction_commons.redis.DistributedTransactionRedisTools;
import org.ala.distributed_transaction_commons.redis.KeyNamespace;
import org.slf4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.NamedThreadLocal;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;


/**
 * 分布式事务工厂
 * 
 * @author 骆毅(ala)
 * @date 2024年2月17日
 */
public abstract class AbstractDistributedTransactionFactory<T extends AbstractDistributedTransaction> implements ApplicationContextAware {
	
	
	static Logger log = LoggerFactory.getLogger("distributed_transaction/transaction", "factory");
	
	
	/**
	 * spring环境
	 */
	protected static ApplicationContext applicationContext;
	
	
	/**
	 * redis工具
	 */
	protected DistributedTransactionRedisTools distributedTransactionRedisTools;
	/**
	 * 协调者
	 */
	protected Coordinator coordinator;
	/**
	 * 发送者
	 */
	protected Participant participant;
	
	
	/**
	 * 线程环境变量 - 分布式事务id
	 * 格式：${uuid}:${当前时间戳} 
	 */
	static final ThreadLocal<String> idResources = new NamedThreadLocal<>("distributed_transactional_id");


	@Override
	public final void setApplicationContext(ApplicationContext ac) throws BeansException {
		applicationContext = ac;
		doSetApplicationContext(ac);
	}
	protected void doSetApplicationContext(ApplicationContext ac) throws BeansException {}
	
	
	/**
	 * biz的格式要求
	 * <p>	字母数字或下划线
	 */
	static Pattern bizPattern = Pattern.compile("[\\w|\\d|_]+");
	/**
	 * 检测biz格式是否合法
	 */
	protected void checkBiz(String biz) {
		Matcher matcher = bizPattern.matcher(biz);
		Assert.isTrue(matcher.matches(), "biz只能由字母或数字或下划线组成 请检查biz:" + biz);
	}
	
	
	/**
	 * 往当前线程环境中放入事务id
	 */
	public static void setTransactionIdToThreadLocal(String txId) {
		idResources.set(txId);
	}
	/**
	 * 清除当前线程环境中的事务id
	 */
	public static void removeTransactionIdToThreadLocal() {
		idResources.remove();
	}
	
	
	/**
	 * 构建分布式事务
	 * @
	 */
	public T build(String biz) {
		return build(biz, KeyNamespace.TIMEOUT * 1000L);
	}
	/**
	 * 开启分布式事务
	 * @
	 */
	public T build(String biz, Long timeout) {
		return build(biz, timeout, 1, 1, 1, 1);
	}
	/**
	 * 构建分布式事物
	 * @param	timeout		事务超时时间（单位：毫秒）
	 * @
	 */
	public T build(String biz, 
					Long timeout, 
					Integer minParticipantCountBegin, 
					Integer minParticipantCountCanCommit, 
					Integer minParticipantCountPreCommit,
					Integer minParticipantCountCommit) {
		//	取一次协调者与参与者，让bean初始化
		getRedisTools();
		getCoordinator();
		getParticipant();
		
		//	从当前环境中拿分布式事务id，如果拿不到则新增一个
		String tid = idResources.get();
		
		//	如果当前线程环境中拿不到，则判定为新开事务
		//	此时第一个参与者额外负责协调者的任务
		T t = null;
		if (tid == null) {
			tid = UUID.randomUUID().toString() + "-" + System.currentTimeMillis();
			//	初始化事务环境，并初始化当前第一个参与者
			t = buildTransaction();
			t.setBiz(biz);
			checkBiz(t.biz());
			log.info("[" + this.getClass().getSimpleName() + " build] 当前线程环境中检测不到事务id，判定为新开分布式事务 biz:" + t.biz() + " txId:" + tid);
			t.setId(tid);
			t.setTimeout(timeout);
			t.setMinParticipantCountBegin(minParticipantCountBegin);
			t.setMinParticipantCountCanCommit(minParticipantCountCanCommit);
			t.setMinParticipantCountPreCommit(minParticipantCountPreCommit);
			t.setMinParticipantCountCommit(minParticipantCountCommit);
			
			appendTransactionData(true, t);
			//	初始化事务环境
			getRedisTools().initTransactionContext(t);
		}
		//	否则判定为已有事物参与者
		else {
			//	构造事物对象，并给当前事物id
			t = buildTransaction();
			t.setBiz(biz);
			checkBiz(t.biz());
			log.info("[" + this.getClass().getSimpleName() + " build] 当前线程环境中检测到事务id，判定为融入当前分布式事务 biz:" + t.biz() + " txId:" + tid);
			t.setId(tid);
			t.setTimeout(timeout);
			t.setMinParticipantCountBegin(minParticipantCountBegin);
			t.setMinParticipantCountCanCommit(minParticipantCountCanCommit);
			t.setMinParticipantCountPreCommit(minParticipantCountPreCommit);
			t.setMinParticipantCountCommit(minParticipantCountCommit);
			
			t.setId(tid);
			appendTransactionData(false, t);
			//	保存当前事物参与者
			getRedisTools().saveParticipant(t);
		}
		//	将txId写回当前线程环境
		idResources.set(t.id());
		return t;
	}

	
	/**
	 * 初始化新事物对象
	 */
	protected abstract T buildTransaction();
	/**
	 * 给事务对象追加数据
	 */
	protected void appendTransactionData(boolean isCoordinator, T t) {
		//	参与者id
		if (StringUtils.isEmpty(t.participantId())) {t.setParticipantId(UUID.randomUUID().toString());}
		//	角色
		t.setRole(isCoordinator ? DistributedTransactionRole.COORDINATOR : DistributedTransactionRole.PARTICIPANT);
		//	状态
		t.setStatus(DistributedTransactionStatus.BEGIN);
		//	开始时间戳
		t.setStartTimestamp(System.currentTimeMillis());
		//	工具引用
		t.setDistributedTransactionRedisTools(getRedisTools());
		t.setCoordinatorPublisher(getParticipant().getCoordinatorPublisher());
		t.setCoordinatorActionExector(getCoordinator().getExector());
	}
	
	
	/**
	 * 取redis工具
	 */
	protected DistributedTransactionRedisTools getRedisTools() {
		if (distributedTransactionRedisTools == null) {
			distributedTransactionRedisTools = applicationContext.getBean(DistributedTransactionRedisTools.class);
		}
		return distributedTransactionRedisTools;
	}
	/**
	 * 取协调者
	 */
	protected Coordinator getCoordinator() {
		if (coordinator == null) {
			coordinator = applicationContext.getBean(Coordinator.class);
		}
		return coordinator;
	}
	/**
	 * 取参与者
	 */
	protected Participant getParticipant() {
		if (participant == null) {
			participant = applicationContext.getBean(Participant.class);
		}
		return participant;
	}
}
